package com.nlelpct.manage.filter;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson2.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nlelpct.manage.constant.ResponseCode;
import com.nlelpct.manage.constant.SecurityConstant;
import com.nlelpct.manage.constant.SystemConstant;
import com.nlelpct.manage.context.ThreadParameter;
import com.nlelpct.manage.enums.ResultEnum;
import com.nlelpct.manage.model.entity.User;
import com.nlelpct.manage.model.security.MyUser;
import com.nlelpct.manage.model.vo.ResponseResult;
import com.nlelpct.manage.utils.DateTimeUtils;
import com.nlelpct.manage.utils.MD5Utils;
import com.nlelpct.manage.utils.ResponseUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author :小马
 * @date : 2023-07-12
 */
@Slf4j
public class LoginFilter extends UsernamePasswordAuthenticationFilter {

    private final RedisTemplate<String, Object> redisTemplate;


    public LoginFilter(RedisTemplate<String, Object> redisTemplate, AuthenticationManager authenticationManager) {
        super(authenticationManager);
        this.redisTemplate = redisTemplate;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        //判断是否是post请求
        if (!HttpMethod.POST.name().equals(request.getMethod())) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }
        // 判断是否是JSON请求类型
        if (!ObjectUtil.isEmpty(request.getContentType()) && request.getContentType().equalsIgnoreCase(MediaType.APPLICATION_JSON_VALUE)) {
            try {
                Map<String, String> map = new ObjectMapper().readValue(request.getInputStream(), Map.class);
                String username = map.get(getUsernameParameter());
                String password = map.get(getPasswordParameter());
                // 看该手机号是否被临时禁用
                if (isThisPhoneForbid(username)) {
                    throw new AuthenticationServiceException("错误次数过多,请5分钟后重试");
                }
                ThreadParameter.getLocalMap().put(SecurityConstant.USERNAME, username);
                UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username, MD5Utils.getMD5String(password));
                setDetails(request, authRequest);
                return this.getAuthenticationManager().authenticate(authRequest);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return super.attemptAuthentication(request, response);
    }

    private boolean isThisPhoneForbid(String username) {
        Integer integer = Optional.ofNullable((Integer) redisTemplate.opsForValue().get(SystemConstant.FORBIDDEN_PRE + username)).orElse(0);
        return integer >= 5;
    }

    /**
     * 认证成功
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) {
        // 登录成功,校验是否90天未登录
        User user = JSON.parseObject(ThreadParameter.getLocalMap().get(SystemConstant.USER_INFO), User.class);
        Long intervalDays = DateTimeUtils.calculateDateTimeIntervalDays(Optional.ofNullable(user.getLastResetPasswd()).orElse(LocalDateTime.now()), LocalDateTime.now());
        if (intervalDays > SystemConstant.MAX_PASSWORD_NOT_MODIFIED_TIME) {
            ResponseUtils.out(response, ResponseResult.error(ResultEnum.PASSWORD_LONGTIME_NOT_MODIFIED));
            return;
        }
        MyUser myUser = (MyUser) authResult.getPrincipal();
        String token = UUID.randomUUID().toString().replaceAll("-", "");
        String oldToken = (String) redisTemplate.opsForValue().get(myUser.getUserId());
        if (!ObjectUtil.isEmpty(oldToken)) {
            redisTemplate.delete(oldToken);
        }
        redisTemplate.opsForValue().set(token, JSON.toJSONString(myUser), SecurityConstant.TOKEN_TIME, TimeUnit.MINUTES);
        redisTemplate.opsForValue().set(myUser.getUserId(), token, SecurityConstant.TOKEN_TIME, TimeUnit.MINUTES);
        redisTemplate.delete(SystemConstant.FORBIDDEN_PRE + myUser.getUsername());
        try {
            user.setLastLogin(LocalDateTime.now());
            user.updateById();
        } catch (Exception e) {
            log.error("更新用户[{}]最后登录时间失败", user.getPhone());
        }
        // todo 登录成功，记录日志
        ResponseUtils.out(response, ResponseResult.success(token, "认证成功"));
    }

    /**
     * 认证失败
     */
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) {
        log.error("unsuccessfulAuthentication==={}", failed.getMessage());
        if (ObjectUtil.isEmpty(ThreadParameter.getLocalMap().get(SystemConstant.OTHER_ERROR))) {
            forbidPhonePlusOne(ThreadParameter.getLocalMap().get(SecurityConstant.USERNAME));
        }
        ResponseUtils.out(response, ResponseResult.error(ResponseCode.ORDINARY_FAIL, failed.getMessage()));
    }


    private void forbidPhonePlusOne(String phone) {
        if (ObjectUtil.isEmpty(phone)) {
            return;
        }
        Integer integer = Optional.ofNullable((Integer) redisTemplate.opsForValue().get(SystemConstant.FORBIDDEN_PRE + phone)).orElse(0);
        redisTemplate.opsForValue().set(SystemConstant.FORBIDDEN_PRE + phone, ++integer, SystemConstant.EXPIRED_TIME_5M, TimeUnit.MINUTES);
    }
}
